#!/bin/bash
#
# Copyright 2015-2017 Adrian DC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

# === Standalone Source Helper ===
# source <(curl -Ls https://github.com/AdrianDC/android_development_shell_tools/raw/master/android_gerritssh.rc)

# === Gerrit SSH ===
function gerritssh()
{
  # Usage: gerritssh [branch] [change_id_reverser] (Advanced Gerrit SSH interface)

  # Gerrit Credentials
  if [ -z "${GerritUsername}" ]; then
    export GerritReview='LineageOS';
    export GerritRemote='review.lineageos.org';
    export GerritUsername='Username';
    export GerritHTTPUsername='Username';
    export GerritHTTPPassword='abcdefgh12345678';
    export GerritBranchPrimary='lineage-15.0';
    export GerritBranchSecondary='cm-14.1';
  fi;

  # Create .bash_android.gerrit.rc with the exports to override the credentials
  if [ -f "${ANDROID_DEVELOPMENT_SHELL_TOOLS_WORKSPACE:-${HOME}}/.bash_android.gerrit.rc" ]; then
    source "${ANDROID_DEVELOPMENT_SHELL_TOOLS_WORKSPACE:-${HOME}}/.bash_android.gerrit.rc";
  fi;

  # Manual user login if credentials missing
  if [ "${GerritUsername}" = 'Username' ]; then

    # Header data
    echo '';
    echo -e '    \e[1;37m\e[4;37mShell Gerrit Reviewer by Adrian DC - 2015-2017\e[0m';
    echo '';
    echo -e "      \e[1;33mError: No configuration in ${ANDROID_DEVELOPMENT_SHELL_TOOLS_WORKSPACE:-${HOME}}/.bash_android.gerrit.rc found";
    echo -e '             Read android_gerritssh.rc script for more details\e[0m';
    echo '';
    echo -e '      \e[1;32mInfo: Gerrit SSH credentials will be used to login and review';
    echo -e '             HTTP credentials will only be used for the "Publish" feature\e[0m';
    echo '';

    # Credentials acquisition
    local input;
    echo -e "      \e[1;37mServer:\e[0m ${GerritRemote}";
    echo -en  '      \e[1;37mUsername:\e[0m ';
    read -r input;
    export GerritUsername=${input};
    export GerritHTTPUsername=${input};
    echo -en  '      \e[1;37mPassword (HTTP):\e[0m ';
    read -r input;
    export GerritHTTPPassword=${input};

    # Credentials ready
    echo '';
    echo -e '      \e[1;33mWarning: Credentials remembered only for this session\e[0m';
    sleep 3;
  fi;

  # Configurations
  local gerritssh="ssh -p 29418 ${GerritUsername}@${GerritRemote} gerrit";
  local projectbranch=${1:-${GerritBranchPrimary}};
  local projectreversemerge=${2};
  local recursivecall=${3};
  local projectbranchmerger=false;

  # Support reverse input only
  if [ -z "${projectbranch//[0-9]/}" ]; then
    projectreversemerge=${projectbranch};
    projectbranch=${GerritBranchPrimary};
  fi;

  # Splash Text
  echo '';
  echo -e "    \e[1;37mShell Gerrit Reviewer by Adrian DC - Connecting to ${projectbranch}\e[0m";

  # Project Loading
  local projectorigin;
  local projectname;
  local tmpdir;
  projectorigin=$(git remote -v | grep origin | tail -n 1);
  if [ -z "${projectorigin}" ]; then
    projectorigin=$(git remote -v | grep ${GerritReview} | tail -n 1);
  fi;
  projectname=$(echo "${projectorigin}" | cut -f 2 | sed "s/.*\/\([^.]*\).* .*/\1/");
  tmpdir=$(mktemp -d);

  # Project Temp Files
  local tmpfileidscount;
  local tmpfileidslist=();
  local tmpfilestate=${tmpdir}/state;
  local tmpfileids=${tmpdir}/ids;
  local tmpfileidsfor=${tmpdir}/'for';
  local tmpfilebranch=${tmpdir}/branch;
  local tmpfilenames=${tmpdir}/names;
  local tmpfileparents=${tmpdir}/parents;
  local tmpfilepatchsets=${tmpdir}/ps;
  local tmpfilepatchinfo=${tmpdir}/pi;
  local tmpfilepatchmessage=${tmpdir}/pm;
  local tmpfilepatchdrafts=${tmpdir}/drafts;

  # Query gerrit through ssh
  if ! ${gerritssh} query \
      --current-patch-set "status:open project:${GerritReview}/${projectname} branch:${projectbranch}" \
      > "${tmpfilestate}"; then
    echo '';
    echo -e "     \e[1;31m> Error... '${gerritssh}' did not connect properly\e[0m";
    echo '';
    rm -rf "${tmpdir}";
    return;
  fi;

  # Parse relevant informations
  grep -E '^\  number' "${tmpfilestate}" | cut -d' ' -f4- > "${tmpfileids}";
  grep -E '^\  branch' "${tmpfilestate}" | cut -d' ' -f4- > "${tmpfilebranch}";
  grep -E '^\  subject' "${tmpfilestate}" | cut -d' ' -f4- > "${tmpfilenames}";
  grep -E '^\ \[' "${tmpfilestate}" | sed 's/.*\[\(.*\)\].*/\1/' > "${tmpfileparents}";
  grep -E '^\    number' "${tmpfilestate}" | cut -d' ' -f6- > "${tmpfilepatchsets}";
  grep -E '^\    isDraft' "${tmpfilestate}" | cut -d' ' -f6- > "${tmpfilepatchdrafts}";
  sort "${tmpfileids}" > "${tmpfileidsfor}";

  # Handle commit reverse merging
  if [ ! -z "${projectreversemerge}" ]; then

    # Read remote current HEAD
    git fetch "https://${GerritRemote}/${GerritReview}/${projectname}" "${projectbranch}" 2> /dev/null;
    local remotecurrenthead;
    remotecurrenthead=$(git rev-parse FETCH_HEAD);

    # Reverse merging variables
    local tmpcommitid=${projectreversemerge};
    local tmpcommitidsearch;
    local tmpcommitpos;
    local tmpcommitparent;
    export tmpcommitspattern=;

    # Parse commits SHA1
    local tmpfilesha1=${tmpdir}/sha1;
    grep -E '^\    revision' "${tmpfilestate}" | cut -d' ' -f6- > "${tmpfilesha1}";

    # Parse the reverse indexes
    while true; do
      tmpcommitpos=$(grep -n "${tmpcommitid}" "${tmpfileids}" | cut -d : -f 1);

      # Exit on failed reverse search
      if [ -z "${tmpcommitpos}" ]; then
        echo '';
        echo -e "     \e[1;31m> Aborting... '${projectreversemerge}' could not be reversed to HEAD\e[0m";
        echo -e "     \e[1;33m> Commit '${tmpcommitid}' was not found on this project\e[0m";
        echo '';
        rm -rf "${tmpdir}";
        return;
      fi;

      # Commit parent and next child search
      tmpcommitparent=$(sed "${tmpcommitpos}!d" "${tmpfileparents}");
      tmpcommitidsearch=$(grep -n "${tmpcommitparent}" "${tmpfilesha1}" | cut -d : -f 1);

      # Reverse to HEAD successful
      if [ "${tmpcommitparent}" = "${remotecurrenthead}" ]; then
        export tmpcommitspattern="${tmpcommitpos} ${tmpcommitspattern}";
        break;

      # Valid ID available to continue
      elif [ ! -z "${tmpcommitidsearch}" ]; then
        tmpcommitid=$(sed "${tmpcommitidsearch}!d" "${tmpfileids}");
        export tmpcommitspattern="${tmpcommitpos} ${tmpcommitspattern}";

      # Exit on failed reverse search
      elif [ -z "${tmpcommitparent}" ] || [ -z "${tmpcommitidsearch}" ]; then
        echo '';
        echo -e "     \e[1;31m> Aborting... '${projectreversemerge}' could not be reversed to HEAD\e[0m";
        echo -e "     \e[1;33m> Commit '${tmpcommitid}' has no valid parent ${tmpcommitparent}\e[0m";
        echo '';
        rm -rf "${tmpdir}";
        return;
      fi;
    done;

    # Prepare gerrit state files
    tmpcommitparent=$(sed "${tmpcommitpos}!d" "${tmpfileparents}");
    mv "${tmpfileids}" "${tmpfileids}.tmp";
    mv "${tmpfilebranch}" "${tmpfilebranch}.tmp";
    mv "${tmpfilenames}" "${tmpfilenames}.tmp";
    mv "${tmpfileparents}" "${tmpfileparents}.tmp";
    mv "${tmpfilepatchsets}" "${tmpfilepatchsets}.tmp";
    mv "${tmpfilepatchdrafts}" "${tmpfilepatchdrafts}.tmp";
    touch "${tmpfileids}";
    touch "${tmpfilebranch}";
    touch "${tmpfilenames}";
    touch "${tmpfileparents}";
    touch "${tmpfilepatchsets}";
    touch "${tmpfilepatchdrafts}";

    # Regenerate gerrit files
    for lineid in ${tmpcommitspattern}; do
      sed "${lineid}!d" "${tmpfileids}.tmp" >> "${tmpfileids}";
      sed "${lineid}!d" "${tmpfilebranch}.tmp" >> "${tmpfilebranch}";
      sed "${lineid}!d" "${tmpfilenames}.tmp" >> "${tmpfilenames}";
      sed "${lineid}!d" "${tmpfileparents}.tmp" >> "${tmpfileparents}";
      sed "${lineid}!d" "${tmpfilepatchsets}.tmp" >> "${tmpfilepatchsets}";
      sed "${lineid}!d" "${tmpfilepatchdrafts}.tmp" >> "${tmpfilepatchdrafts}";
    done;
    cp "${tmpfileids}" "${tmpfileidsfor}";
    export tmpcommitspattern=;
    echo '';
  fi;

  # Branch empty
  if [ ! -s "${tmpfileidsfor}" ]; then
    echo '';
    echo -en  '     \e[1;32m> No commit on review. ';

    # Exit on second execution
    if [ ! -z "${recursivecall}" ]; then
      echo -e 'Exiting...\e[0m';
      echo '';
      rm -rf "${tmpdir}";
      return;
    fi;

    # Switch branches
    if [[ "${projectbranch}" == "${GerritBranchPrimary}" ]]; then
      projectbranch=${GerritBranchSecondary};
    elif [[ "${projectbranch}" == "${GerritBranchSecondary}" ]]; then
      projectbranch=${GerritBranchPrimary};
    fi;
    echo -e "Moving to ${projectbranch}...\e[0m";
    rm -rf "${tmpdir}";
    gerritssh "${projectbranch}" '' 'true';
    return;

  fi;

  # Project Connected
  echo -e \\033c;
  clear;
  echo '';
  echo -e " \e[1;31m\e[4;31mConnected to Gerrit - ${GerritReview}/${projectname} - ${projectbranch}\e[0m";
  echo '';
  echo -e '    \e[1;37m\e[4;37mShell Gerrit Reviewer by Adrian DC - 2015-2017\e[0m';
  echo -e '      \e[1;37mCommands : y (Yes) / n (No) / q (Quit)';
  echo -e '                 p (Publish, +1+2 only)';
  echo -e '                 s (+1+2 Submit commit)';
  echo -e '                 a (+1+2 Submit all)';
  echo -e '                 u (Only undraft all)\e[0m';


  # Commits on Review
  tmpfileidscount=$(wc -w "${tmpfileidsfor}");
  echo '';
  echo -e "  \e[1;34m\e[4;34mChanges on Review (${tmpfileidscount} commits)\e[0m";
  while IFS= read -r i; do

    # Commit Variables
    local patchbranch;
    local patchname;
    local patchset;
    local patchdraft;

    # Commit Details
    local gerritidpos=-1;
    local gerritidcount=0;
    while IFS= read -r j; do
      gerritidcount=$((gerritidcount+1));
      if [[ "${i}" == "${j}" ]]; then
        gerritidpos=${gerritidcount};
        break;
      fi;
    done < "${tmpfileids}";
    patchbranch=$(sed ${gerritidpos}'!d' "${tmpfilebranch}");
    patchname=$(sed ${gerritidpos}'!d' "${tmpfilenames}");
    patchset=$(sed ${gerritidpos}'!d' "${tmpfilepatchsets}");
    patchdraft=$(sed ${gerritidpos}'!d' "${tmpfilepatchdrafts}");
    if [[ ! "${patchdraft}" == 'false' ]]; then
      patchdraft=' [DRAFT]';
    else
      patchdraft=;
    fi;

    # Commit details
    echo -e "    \e[1;33m${i}/${patchset}:\e[0m ${patchname}${patchdraft}";

    # Commits list
    tmpfileidslist+=("${i}");

  done < "${tmpfileidsfor}";
  echo '';

  # Commands variables
  local cmd_global=;
  local cmd_rebase=;
  local cmd_publish=;
  local cmd_review=;
  local cmd_submit=;
  local key;

  # Commits Selection
  for i in "${tmpfileidslist[@]}"; do

    # Commit Variables
    local patchbranch;
    local patchname;
    local patchparent;
    local patchset;
    local patchdraft;

    # Commit Details
    local gerritidpos=-1;
    local gerritidcount=0;
    while IFS= read -r j; do
      gerritidcount=$((gerritidcount+1));
      if [[ "${i}" == "${j}" ]]; then
        gerritidpos=${gerritidcount};
        break;
      fi;
    done < "${tmpfileids}";
    patchbranch=$(sed "${gerritidpos}!d" "${tmpfilebranch}");
    patchname=$(sed "${gerritidpos}!d" "${tmpfilenames}");
    patchparent=$(sed "${gerritidpos}!d" "${tmpfileparents}");
    patchset=$(sed "${gerritidpos}!d" "${tmpfilepatchsets}");
    patchdraft=$(sed "${gerritidpos}!d" "${tmpfilepatchdrafts}");

    # Commit Work
    echo '';
    echo -e "  \e[1;34m\e[4;34mChange ${i}/${patchset}\e[0m";
    echo "    \"${patchname}\"";
    echo '';
    echo -en  "    \e[1;32m> Work on commit ${patchbranch}/${i} (y/N/p/s/a/u/q) ?\e[0m ";
    if [ -z "${cmd_global}" ]; then
      read -r key;
    else
      key=${cmd_global};
      echo "${key}";
    fi;
    if [[ 'qQ' == *"${key}"* ]]; then
      break;
    elif [[ ! 'yYpPsSaAuU' == *"${key}"* ]]; then
      continue;
    fi;

    # Commands selection
    case ${key} in
      p|P)
        cmd_rebase=n;
        cmd_publish=y;
        cmd_review=y;
        cmd_submit=n;
        ;;
      s|S)
        cmd_rebase=y;
        cmd_publish=y;
        cmd_review=y;
        cmd_submit=y;
        ;;
      a|A)
        cmd_global=${key};
        cmd_rebase=n;
        cmd_publish=y;
        cmd_review=y;
        cmd_submit=y;
        ;;
      u|U)
        cmd_global=${key};
        cmd_rebase=n;
        cmd_publish=y;
        cmd_review=n;
        cmd_submit=n;
        ;;
      *)
        ;;
    esac;

    # Study rebasing if needed
    if [[ ! "${cmd_rebase}" == 'n' ]]; then

      # Read remote current HEAD
      local remotecurrenthead;
      git fetch "https://${GerritRemote}/${GerritReview}/${projectname}" "${projectbranch}" 2> /dev/null;
      remotecurrenthead=$(git rev-parse FETCH_HEAD);

      # Commit Rebase
      if [[ ! "${remotecurrenthead}" == "${patchparent}" ]]; then
        echo -en  "      \e[1;31m> Rebase on HEAD  \e[1;37m${i}/${patchset}\e[1;31m (y/N) ?\e[0m ";
        if [ -z "${cmd_rebase}" ]; then
          read -r key;
        else
          echo "${cmd_rebase}";
          key=${cmd_rebase};
        fi;
        if [[ "${key}" == 'y' || "${key}" == 'Y' ]]; then
          ${gerritssh} review --rebase --project "${GerritReview}/${projectname}" "${i},${patchset}";
          patchset=$(${gerritssh} query --current-patch-set "status:open project:${GerritReview}/${projectname} change:${i}" \
                   | grep -E '^\    number' \
                   | cut -d' ' -f6-);
        fi;
      fi;
    fi;

    # Commit Draft Publish
    if [[ ! "${patchdraft}" == 'false' ]]; then
      echo -en  "      \e[1;31m> Publish draft   \e[1;37m${i}/${patchset}\e[1;31m (y/N) ?\e[0m ";
      if [ -z "${cmd_publish}" ]; then
        read -r key;
      else
        echo "${cmd_publish}";
        key=${cmd_publish};
      fi;
      if [[ "${key}" == 'y' || "${key}" == 'Y' ]]; then
        ${gerritssh} review --publish --project "${GerritReview}/${projectname}" "${i},${patchset}";
      fi;
    fi;

    # Commit Branch Merger
    if [[ ! "${projectbranchmerger}" == 'false' ]]; then

      # Commit Cherry-Pick Branch
      local projectbranchcp=;
      if [[ "${projectbranch}" == "${GerritBranchPrimary}" ]]; then
        projectbranchcp=${GerritBranchSecondary};
      elif [[ "${projectbranch}" == "${GerritBranchSecondary}" ]]; then
        projectbranchcp=${GerritBranchPrimary};
      fi;

      # Commit Cherry-Pick Commit
      if [ ! -z "${projectbranchcp}" ]; then
        echo -en  "      \e[1;31m> Copy to ${projectbranchcp} \e[1;37m${i}/${patchset}\e[1;31m (y/N) ?\e[0m ";
        read -r key;
        if [[ "${key}" == 'y' || "${key}" == 'Y' ]]; then

          # Commit variables
          local patchcommit;
          local patchmessage;

          # Get Commit Details
          ${gerritssh} query --current-patch-set "status:open project:${GerritReview}/${projectname} change:${i}" > "${tmpfilepatchinfo}";
          patchcommit=$(grep -E '^\    revision' "${tmpfilepatchinfo}" \
                      | cut -d' ' -f6-);
          # | sed '${d}'
          sed -n '/commitMessage/,/createdOn/ p' "${tmpfilepatchinfo}" \
            | cut -c 18- > "${tmpfilepatchmessage}";
          echo "(cherry picked from commit ${patchcommit})" >> "${tmpfilepatchmessage}";
          patchmessage=$(sed 's/\"/\\\"/g' "${tmpfilepatchmessage}" \
                       | sed ':begin;$!N;s/\n/\\n/;tbegin');
          echo "{\"destination\":\"${projectbranchcp}\",\"message\":\"${patchmessage}\"}" > "${tmpfilepatchmessage}";

          # Post Cherry-Pick Request
          curl -X POST --digest \
               -H "Content-Type: application/json" \
               --data @"${tmpfilepatchmessage}" \
               --user "${GerritHTTPUsername}:${GerritHTTPPassword}" \
               "http://review.lineageos.org/a/changes/${i}/revisions/${patchcommit}/cherrypick";
        fi;
      fi;
    fi;

    # Commit Review
    echo -en  "      \e[1;31m> Review +1/+2    \e[1;37m${i}/${patchset}\e[1;31m (y/N) ?\e[0m ";
    if [ -z "${cmd_review}" ]; then
      read -r key;
    else
      echo "${cmd_review}";
      key=${cmd_review};
    fi;
    if [[ "${key}" == 'y' || "${key}" == 'Y' ]]; then
      ${gerritssh} review --verified 1 --code-review 2 --project "${GerritReview}/${projectname}" "${i},${patchset}";
    fi;

    # Commit Submission
    echo -en  "      \e[1;31m> Submit commit   \e[1;37m${i}/${patchset}\e[1;31m (y/N) ?\e[0m ";
    if [ -z "${cmd_submit}" ]; then
      read -r key;
    else
      echo "${cmd_submit}";
      key=${cmd_submit};
    fi;
    if [[ "${key}" == 'y' || "${key}" == 'Y' ]]; then
      ${gerritssh} review --submit --project "${GerritReview}/${projectname}" "${i},${patchset}";
    fi;

    # Commit Finished
    echo -e "    \e[1;32m> Done working on ${i}\e[0m ";

  done;

  # End of Project
  rm -rf "${tmpdir}";
  echo '';
  echo -e "  \e[1;37m\e[4;37mDone with ${GerritReview}/${projectname} - ${projectbranch}\e[0m ";
  echo '';
}

